"""投资组合配置加载."""

from __future__ import annotations

from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Iterable, List, Mapping

import yaml

from src.utils import normalize_symbol


@dataclass(frozen=True)
class PositionEntry:
    """单个持仓配置."""

    codes: List[str]
    description: str

    def all_keys(self) -> set[str]:
        """返回用于匹配的所有代码形式."""

        keys: set[str] = set()
        for code in self.codes:
            if not code:
                continue
            stripped = code.strip()
            if stripped:
                keys.add(stripped)
                keys.add(stripped.upper())
                try:
                    keys.add(normalize_symbol(stripped))
                except ValueError:
                    # 非法代码保持原样
                    pass
        return keys


@dataclass(frozen=True)
class PortfolioConfig:
    """持仓配置."""

    default_description: str
    positions: List[PositionEntry]

    def find_description(self, fund: str) -> str:
        """根据基金代码查找持仓说明."""

        normalized = normalize_symbol(fund)
        candidates = {fund, fund.strip(), fund.strip().upper(), normalized}
        for entry in self.positions:
            if candidates & entry.all_keys():
                return entry.description
        return self.default_description


POSITIONS_FILE = Path(__file__).with_name("positions.yaml")


def _load_raw_positions() -> Mapping[str, object]:
    if not POSITIONS_FILE.exists():
        return {}
    with POSITIONS_FILE.open("r", encoding="utf-8") as fp:
        data = yaml.safe_load(fp) or {}
    if not isinstance(data, Mapping):
        raise ValueError("positions.yaml 应为字典结构")
    return data


def _parse_positions(entries: Iterable[Mapping[str, object]]) -> List[PositionEntry]:
    parsed: List[PositionEntry] = []
    for entry in entries:
        if not isinstance(entry, Mapping):
            continue
        codes = entry.get("codes") or []
        if isinstance(codes, str):
            codes = [codes]
        if not isinstance(codes, Iterable):
            codes = []
        codes_list = [str(code).strip() for code in codes if str(code).strip()]
        description = entry.get("description")
        description_text = str(description).strip() if description else ""
        if codes_list and description_text:
            parsed.append(PositionEntry(codes=codes_list, description=description_text))
    return parsed


@lru_cache(maxsize=1)
def load_portfolio_config() -> PortfolioConfig:
    """加载并缓存持仓配置."""

    raw = _load_raw_positions()
    default_section = raw.get("default", {})
    default_desc = ""
    if isinstance(default_section, Mapping):
        default_value = default_section.get("description")
        if isinstance(default_value, str):
            default_desc = default_value.strip()
    if not default_desc:
        default_desc = "未配置持仓信息，请在 config/positions.yaml 中维护。"

    positions_section = raw.get("positions") or []
    if isinstance(positions_section, Mapping):
        positions_section = [positions_section]
    entries: List[PositionEntry] = []
    if isinstance(positions_section, Iterable):
        entries = _parse_positions(
            item for item in positions_section if isinstance(item, Mapping)
        )

    return PortfolioConfig(default_description=default_desc, positions=entries)


def get_position_description(fund: str) -> str:
    """获取基金对应的持仓描述."""

    if not fund:
        return load_portfolio_config().default_description
    config = load_portfolio_config()
    try:
        return config.find_description(fund)
    except ValueError:
        # normalize_symbol 可能在输入非法时抛错，返回默认描述
        return config.default_description

